/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.meanzoft.minecraft_clone.test;

import org.meanzoft.minecraft_clone.block2.Block;
import org.meanzoft.minecraft_clone.util.VertexBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBVertexBufferObject;
import org.lwjgl.opengl.GLContext;
import static org.lwjgl.opengl.GL11.*;

/**
 *
 * @author Meanz
 */
public class VBO {

    /**
     * Internal Attributes
     */
    private int vboIndices = 0;
    private int vboData = 0;
    public VertexBuffer vertices;
    public VertexBuffer texCoords;
    private VertexBuffer normals;
    private IntBuffer indices;
    private byte[] blockData;
    private boolean isBuilt = false;

    public VBO() {
        blockData = new byte[4096];
        vertices = new VertexBuffer();
        texCoords = new VertexBuffer();
        normals = new VertexBuffer();
        indices = IntBuffer.allocate(0);
    }
    
    public void setBuilt(boolean isBuilt) {
        this.isBuilt = isBuilt;
    }

    public void draw() {
        if (!isBuilt) {
            return;
        }
        /**********************************************
        Enable the vertex buffer objects/arrays for use
         **********************************************/
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);
        //glEnableClientState(GL_COLOR_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        /**********************************************
        Set which vbo's to use. We will set our vertex
        attributes vbo as the vbo with the vertex data.
        Then we will set the vertex index vbo as the 
        vbo with the vertex indexes.
         **********************************************/
        ARBVertexBufferObject.glBindBufferARB(
                ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB,
                vboData);

        ARBVertexBufferObject.glBindBufferARB(
                ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB,
                vboIndices);

        /**********************************************
        Call the OpenGL command to get the vertex position
        and then use the vertex indexes to draw a quad
        based on those positions. 
        
        This drawing process can get a bit more involved
        once you start adding normals, vertex colors, and
        texture coords. This example is just to show
        how to get a quad on screen. From there you can
        check out other online tutorials which explain how
        to do more things, like adding color to each vertex.
        
         **********************************************/
        //Get the size of primitive types in bytes
        int intSize = Integer.SIZE / 8;
        int floatSize = Float.SIZE / 8;
        int doubleSize = Double.SIZE / 8;
        //Get the size of a 3d vector (in doubles) and RGBA color (in floats)
        int verticeSize = 3 * floatSize;
        int normalSize = 3 * floatSize;
        int texSize = 2 * floatSize;

        //Get the size of an element
        int elementSize = verticeSize + normalSize + texSize;

        // Get the vertex position data
        //the vertex pointer has an offset of 0 and a stride of elementSize
        glVertexPointer(3, GL_FLOAT, elementSize, 0);
        //the normal pointer has an offset of verticeSize and a stride of elementSize
        glNormalPointer(GL_FLOAT, elementSize, verticeSize);
        //the color pointer has an offset of (verticeSize+normalSize) and a stride of elementSize
        glTexCoordPointer(2, GL_FLOAT, elementSize, verticeSize + normalSize);
        // Draw the quad using the vertex indexes
        glDrawElements(GL_QUADS, vertices.size(), GL_UNSIGNED_INT, 0);

        /**********************************************
        Unset/UnBind the quads vertex buffer objects
         **********************************************/
        ARBVertexBufferObject.glBindBufferARB(
                ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB,
                0);

        ARBVertexBufferObject.glBindBufferARB(
                ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB,
                0);

        /**********************************************
        Disable the vertex buffer objects/arrays
         **********************************************/
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_NORMAL_ARRAY);
        //glDisableClientState(GL_COLOR_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }

    /**
     * VBO's
     */
    public int createVBOID() {
        if (GLContext.getCapabilities().GL_ARB_vertex_buffer_object) {
            IntBuffer buffer = BufferUtils.createIntBuffer(1);
            ARBVertexBufferObject.glGenBuffersARB(buffer);
            return buffer.get(0);
        }
        return 0;
    }

    public void bufferData(int id, FloatBuffer buffer) {
        if (GLContext.getCapabilities().GL_ARB_vertex_buffer_object) {
            ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, id);
            ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, buffer, ARBVertexBufferObject.GL_STATIC_DRAW_ARB);
        }
    }

    public static void bufferElementData(int id, IntBuffer buffer) {
        if (GLContext.getCapabilities().GL_ARB_vertex_buffer_object) {
            ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB, id);
            ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB, buffer, ARBVertexBufferObject.GL_STATIC_DRAW_ARB);
        }
    }

    public void compile() {
        bufferVBO();
    }

    public void bufferVBO() {
        /**
         * Buffer vertices
         */
        //Get the size of primitive types in bytes
        int intSize = Integer.SIZE / 8;
        int floatSize = Float.SIZE / 8;
        int doubleSize = Double.SIZE / 8;
        //Get the size of a 3d vector (in doubles) and RGBA color (in floats)
        int verticeSize = 3 * floatSize;
        int normalSize = 3 * floatSize;
        int texSize = 2 * floatSize;

        //Get the size of an element
        int elementSize = verticeSize + normalSize + texSize;
        vboIndices = createVBOID();
        vboData = createVBOID();
        ByteBuffer bb = ByteBuffer.allocateDirect(vertices.size() * elementSize);
        bb.order(ByteOrder.nativeOrder());
        for (int vert = 0; vert < vertices.size(); vert++) {
            if (vertices.getVertex(vert) == null) {
                continue;
            }
            float[] vertex = vertices.getVertex(vert).toFloatArray();
            float[] texCoord = texCoords.getVertex(vert).toFloatArray();
            /**
             * Vertices
             */
            for (int coord = 0; coord < 3; coord++) {
                bb.putFloat(vertex[coord]);
            }
            /**
             * Normals
             */
            for (int coord = 0; coord < 3; coord++) {
                bb.putFloat(0);
            }
            /**
             * Tex coords
             */
            for (int coord = 0; coord < 2; coord++) {
                bb.putFloat(texCoord[coord + 1]);
            }
        }
        bb.flip();
        bufferData(vboData, bb.asFloatBuffer());

        /**
         * Buffer indices
         */
        bb = ByteBuffer.allocateDirect(vertices.size() * 4);
        bb.order(ByteOrder.nativeOrder());
        for (int i = 0; i < vertices.size(); i++) {
            bb.putInt(i);
        }
        bb.flip();
        bufferElementData(vboIndices, bb.asIntBuffer());
    }

    public void addBox(float width, float height, float offX, float offY, float offZ, int[] textures, byte faceData) {
        int numFaces = 0;
        for (int i = 0; i < 6; i++) {
            if ((faceData & Block.masks[i]) != Block.masks[i]) {
                numFaces++;
            }
        }
        vertices.expand(numFaces * 4);
        texCoords.expand(numFaces * 4);
        //normals.expand(numFaces);

        float off = (float) ((float) 1 / (float) 16);
        float[] texX = new float[6];
        float[] texY = new float[6];

        for (int i = 0; i < 6; i++) {
            texX[i] = textures[i] * off;
            texY[i] = off * (textures[i] / 16);
        }

        if ((faceData & Block.masks[0]) != Block.masks[0]) {
            texCoords.putVertex(0f, texX[0], texY[0] + off);
            vertices.putVertex(offX + width, offY, offZ);
            texCoords.putVertex(0f, texX[0] + off, texY[0] + off);
            vertices.putVertex(offX, offY, offZ);
            texCoords.putVertex(0f, texX[0] + off, texY[0]);
            vertices.putVertex(offX, height + offY, offZ);
            texCoords.putVertex(0f, texX[0], texY[0]);
            vertices.putVertex(width + offX, height + offY, offZ);
        }
        if ((faceData & Block.masks[1]) != Block.masks[1]) {
            texCoords.putVertex(0f, texX[1], texY[1] + off);
            vertices.putVertex(offX, offY, width + offZ);
            texCoords.putVertex(0f, texX[1] + off, texY[1] + off);
            vertices.putVertex(width + offX, offY, width + offZ);
            texCoords.putVertex(0f, texX[1] + off, texY[1]);
            vertices.putVertex(width + offX, height + offY, width + offZ);
            texCoords.putVertex(0f, texX[1], texY[1]);
            vertices.putVertex(offX, height + offY, width + offZ);
        }
        if ((faceData & Block.masks[2]) != Block.masks[2]) {
            texCoords.putVertex(0f, texX[2], texY[2] + off);
            vertices.putVertex(width + offX, height + offY, offZ);
            texCoords.putVertex(0f, texX[2] + off, texY[2] + off);
            vertices.putVertex(offX, height + offY, offZ);
            texCoords.putVertex(0f, texX[2] + off, texY[2]);
            vertices.putVertex(offX, height + offY, width + offZ);
            texCoords.putVertex(0f, texX[2], texY[2]);
            vertices.putVertex(width + offX, height + offY, width + offZ);
        }
        if ((faceData & Block.masks[3]) != Block.masks[3]) {
            texCoords.putVertex(0f, texX[3], texY[3] + off);
            vertices.putVertex(width + offX, offY, width + offZ);
            texCoords.putVertex(0f, texX[3] + off, texY[3] + off);
            vertices.putVertex(offX, offY, width + offZ);
            texCoords.putVertex(0f, texX[3] + off, texY[3]);
            vertices.putVertex(offX, offY, offZ);
            texCoords.putVertex(0f, texX[3], texY[3]);
            vertices.putVertex(width + offX, offY, offZ);
        }
        if ((faceData & Block.masks[4]) != Block.masks[4]) {
            texCoords.putVertex(0f, texX[4], texY[4] + off);
            vertices.putVertex(width + offX, offY, width + offZ);
            texCoords.putVertex(0f, texX[4] + off, texY[4] + off);
            vertices.putVertex(width + offX, offY, offZ);
            texCoords.putVertex(0f, texX[4] + off, texY[4]);
            vertices.putVertex(width + offX, height + offY, offZ);
            texCoords.putVertex(0f, texX[4], texY[4]);
            vertices.putVertex(width + offX, height + offY, width + offZ);
        }
        if ((faceData & Block.masks[5]) != Block.masks[5]) {
            texCoords.putVertex(0f, texX[5], texY[5] + off);
            vertices.putVertex(offX, offY, offZ);
            texCoords.putVertex(0f, texX[5] + off, texY[5] + off);
            vertices.putVertex(offX, offY, width + offZ);
            texCoords.putVertex(0f, texX[5] + off, texY[5]);
            vertices.putVertex(offX, height + offY, width + offZ);
            texCoords.putVertex(0f, texX[5], texY[5]);
            vertices.putVertex(offX, height + offY, offZ);
        }
    }

    public void clear() {
        texCoords.clear();
        vertices.clear();
        normals.clear();
    }
}
